{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "ec067e31",
   "metadata": {},
   "source": [
    "ElasticNet回归\n",
    "\n",
    "适合时序或一般回归任务。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ffd6fb38",
   "metadata": {},
   "source": [
    "## 1. 导入必要的库\n",
    "\n",
    "导入PyTorch、NumPy、Matplotlib等库。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e22d2ea1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 导入库\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "from sklearn.preprocessing import MinMaxScaler\n",
    "from sklearn.model_selection import train_test_split"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6eaedc6e",
   "metadata": {},
   "source": [
    "## 2. 数据准备与预处理\n",
    "\n",
    "生成模拟原材料消耗数据，归一化，并划分训练集和测试集。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c9cf1524",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 使用葡萄酒数据集的部分特征预测质量\n",
    "import pandas as pd\n",
    "# 下载葡萄酒数据集：https://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv\n",
    "# 假设文件名为 winequality-red.csv，分隔符为';'\n",
    "df = pd.read_csv('winequality-red.csv', sep=';')\n",
    "features = ['fixed acidity', 'pH', 'residual sugar']\n",
    "X = df[features].values\n",
    "y = df['quality'].values\n",
    "\n",
    "plt.figure(figsize=(8, 4))\n",
    "plt.hist(y, bins=range(int(y.min()), int(y.max())+2), edgecolor='black')\n",
    "plt.title('葡萄酒质量分布')\n",
    "plt.xlabel('质量')\n",
    "plt.ylabel('样本数')\n",
    "plt.show()\n",
    "\n",
    "# 归一化\n",
    "tx = MinMaxScaler().fit_transform(X)\n",
    "ty = MinMaxScaler().fit_transform(y.reshape(-1, 1))\n",
    "\n",
    "# 划分训练集和测试集\n",
    "X_train, X_test, y_train, y_test = train_test_split(tx, ty, test_size=0.2, random_state=42)\n",
    "\n",
    "# 转为PyTorch张量\n",
    "X_train = torch.tensor(X_train, dtype=torch.float32)\n",
    "y_train = torch.tensor(y_train, dtype=torch.float32)\n",
    "X_test = torch.tensor(X_test, dtype=torch.float32)\n",
    "y_test = torch.tensor(y_test, dtype=torch.float32)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e6a8607e",
   "metadata": {},
   "source": [
    "## 3. 构建ElasticNet模型\n",
    "\n",
    "定义PyTorch线性回归模型，并实现ElasticNet损失函数（L1+L2正则）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1254baa5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 定义线性回归模型\n",
    "class ElasticNetModel(nn.Module):\n",
    "    def __init__(self, input_dim):\n",
    "        super().__init__()\n",
    "        self.linear = nn.Linear(input_dim, 1)\n",
    "    def forward(self, x):\n",
    "        return self.linear(x)\n",
    "\n",
    "# ElasticNet损失函数\n",
    "class ElasticNetLoss(nn.Module):\n",
    "    def __init__(self, alpha=1.0, l1_ratio=0.5):\n",
    "        super().__init__()\n",
    "        self.alpha = alpha\n",
    "        self.l1_ratio = l1_ratio\n",
    "    def forward(self, y_pred, y_true, model):\n",
    "        mse = nn.functional.mse_loss(y_pred, y_true)\n",
    "        l1 = sum(torch.abs(param).sum() for param in model.parameters())\n",
    "        l2 = sum((param ** 2).sum() for param in model.parameters())\n",
    "        return mse + self.alpha * (self.l1_ratio * l1 + (1 - self.l1_ratio) * l2)\n",
    "\n",
    "model = ElasticNetModel(input_dim=1)\n",
    "loss_fn = ElasticNetLoss(alpha=0.01, l1_ratio=0.5)\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.01)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c8b14e7c",
   "metadata": {},
   "source": [
    "## 4. 训练模型\n",
    "\n",
    "训练ElasticNet回归模型，记录损失变化。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b54c5385",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 训练模型\n",
    "num_epochs = 200\n",
    "losses = []\n",
    "for epoch in range(num_epochs):\n",
    "    model.train()\n",
    "    optimizer.zero_grad()\n",
    "    y_pred = model(X_train)\n",
    "    loss = loss_fn(y_pred, y_train, model)\n",
    "    loss.backward()\n",
    "    optimizer.step()\n",
    "    losses.append(loss.item())\n",
    "    if (epoch+1) % 20 == 0:\n",
    "        print(f\"Epoch {epoch+1}/{num_epochs}, Loss: {loss.item():.4f}\")\n",
    "\n",
    "plt.plot(losses)\n",
    "plt.title('训练损失曲线')\n",
    "plt.xlabel('Epoch')\n",
    "plt.ylabel('Loss')\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "68ef2f4b",
   "metadata": {},
   "source": [
    "## 5. 评估与可视化\n",
    "\n",
    "在测试集上评估模型表现，并可视化真实值与预测值。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "78468070",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 测试集预测与可视化\n",
    "model.eval()\n",
    "with torch.no_grad():\n",
    "    y_pred = model(X_test).numpy()\n",
    "    y_true = y_test.numpy()\n",
    "\n",
    "plt.figure(figsize=(10, 4))\n",
    "plt.plot(y_true, label='真实值')\n",
    "plt.plot(y_pred, label='预测值')\n",
    "plt.legend()\n",
    "plt.title('ElasticNet回归预测结果对比')\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
